/* Simple HTTP Server Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdint.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_eth.h"
#include "esp_tls_crypto.h"
#include <esp_http_server.h>
#include "freertos/event_groups.h"
#include "esp_ota_ops.h"
#include "cJSON.h"
#include "esp_timer.h"
#include "http_server.h"
#include "wifistation.h"
#include "storage.h"

// #define NOTIFY

#define RETRY_COUNT_MAX             (20)
#define CONFIG_EXAMPLE_BASIC_AUTH   (0)
#define UPDATE_PIC_DELAY_TIME       (150)//s
// #define RECEIVE_BUFFER_SIZE             256
#define NET_DATA_SIZE                   1024
/* A simple example that demonstrates how to create GET and POST
 * handlers for the web server.
 */
static const char *TAG = "http-server";
static httpd_handle_t server = NULL;
static uint8_t web_server_init = 0;

uint8_t identifyBuf[32]={0};
#ifdef NOTIFY
extern EventGroupHandle_t s_wifi_event_group;
#else
extern uint8_t need_reboot;
#endif
extern char nvs_identify[];

static uint8_t findValueOfKey(char* origin, char* key, char*value)
{
  char *start = NULL;
  char * end = NULL;
  start = strstr((char*)origin, key);
  if(NULL == start)
  {
    //Serial.printf("start:[%s]\r\n", start);
    return 0;
  }

  end = strstr(start+1, "\r\n--");
  if(NULL != end)
  {
    //Serial.printf("end:[%s]\r\n", end);
    int valeLen = strlen(start) - strlen(end) - strlen(key) - 5;
    //Serial.printf("len %d\r\n", valeLen);
    // memcpy(value, start + strlen(key) + 5, valeLen);
    strncpy(value, start + strlen(key) + 5, valeLen);
    //value[valeLen] = '\n';
    // Serial.printf(value);
    // Serial.printf("=====================\r\n");
  }
  else
  {
    return 0;
  }
  return 1;
}
static void paras_json(cJSON *pRoot, httpd_req_t *req)
{
    char send_buf[256];
    uint16_t delayCount = 0;
    char ssid[64] = {0};
    char pwd[32] = {0};

    cJSON *pSsid = NULL;
    cJSON *pPwd = NULL;
    cJSON *pIdentifi = NULL;
    cJSON *pOnWorkTime = NULL;

    memset(send_buf, 0, 256);
    
    memset(ssid, 0, sizeof(ssid));
    memset(pwd, 0, sizeof(pwd));

    pSsid = cJSON_GetObjectItem(pRoot, "router_ssid");
    pPwd = cJSON_GetObjectItem(pRoot, "router_pwd");
    pIdentifi = cJSON_GetObjectItem(pRoot, "idenfi_id");
    pOnWorkTime = cJSON_GetObjectItem(pRoot, "onwork_time");

    if(pOnWorkTime != NULL)
    {
        if(0 != strlen(pOnWorkTime->valuestring))
        {
            ESP_LOGI(TAG, "---------- onWork Time:%s------------\r\n", pOnWorkTime->valuestring);
        }
    }

    if(pIdentifi != NULL) 
    {
        if(0 != strlen(pIdentifi->valuestring))
        {
            if(0 == strcmp(pIdentifi->valuestring, " "))
            {
                ESP_LOGI(TAG, "----------erase identify id------------\r\n");
                memset(identifyBuf, 0, sizeof(identifyBuf));
                writeConfigToNvs(nvs_identify, strlen(nvs_identify), pIdentifi->valuestring, 0);
                snprintf(send_buf, sizeof(send_buf), "{\"code\":200,\"msg\":\"success\",\"data\":{\"img_target_url\":\"%s\"}}", pIdentifi->valuestring);
                httpd_resp_send(req, send_buf, HTTPD_RESP_USE_STRLEN);
                // need_reboot = 1;
            }
            else
            {
                ESP_LOGI(TAG, "---------- identify id:%s------------\r\n", pIdentifi->valuestring);
                writeConfigToNvs(nvs_identify, strlen(nvs_identify), pIdentifi->valuestring, strlen(pIdentifi->valuestring));
                strncpy((char*)identifyBuf, pIdentifi->valuestring, strlen(pIdentifi->valuestring));
                snprintf(send_buf, sizeof(send_buf), "{\"code\":200,\"msg\":\"success\",\"data\":{\"img_target_url\":\"%s\"}}", pIdentifi->valuestring);
                httpd_resp_send(req, send_buf, HTTPD_RESP_USE_STRLEN);
                need_reboot = 1;
            }
        // httpd_resp_send(req, send_buf, HTTPD_RESP_USE_STRLEN);
        }
    }

    if((pSsid != NULL) && (pPwd != NULL))
    {
        if((0 != strlen(pSsid->valuestring)) && (0 != strlen(pPwd->valuestring)))
        {
            #ifdef NOTIFY
            wifiStationInit(pSsid->valuestring, pPwd->valuestring);
            if (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) {
                httpd_resp_send(req, "{\"code\":200,\"msg\":\"success\",\"data\":{}}", HTTPD_RESP_USE_STRLEN);
            } else {
                httpd_resp_send(req, "{\"code\":200,\"msg\":\"error\",\"data\":{}}", HTTPD_RESP_USE_STRLEN);
            }
            #else
            printf("------------------------\r\n\r\n");
            writeConfigToNvs(nvs_ssid, strlen(nvs_ssid), pSsid->valuestring, strlen(pSsid->valuestring));
            writeConfigToNvs(nvs_pwd, strlen(nvs_pwd), pPwd->valuestring, strlen(pPwd->valuestring));
            need_reboot = 1;
            #endif
        }
    }
}

static esp_err_t paras_form_data(char *data_buf, httpd_req_t *req)
{
    char value[128];
    char resp[512] = {0};
    memset(value, 0, 128);
    // printf("data_buf:[%s]\r\n", data_buf);
    if(findValueOfKey(data_buf, "img_interval", value))
    {
        ESP_LOGI(TAG, "Set img interval :%s\r\n", value);
        snprintf(resp, 512, "{\"code\":200,\"msg\":\"success\",\"data\":{\"beep_func\":\"%s\"}}",value);
        httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
        return ESP_OK;
    }
    httpd_resp_send(req, "SET-OK", HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

/* An HTTP POST handler */
static esp_err_t post_values_handler(httpd_req_t *req)
{
    // uint16_t i = 0;
    // char field_buf[32] = {0};
    char temp_buf[NET_DATA_SIZE];
    // char value[128] = {0};
    cJSON *pRoot = NULL;
    int ret, remaining = req->content_len;
    int total_data = req->content_len;

    ESP_LOGI(TAG, "%s===========  %d  ==========", __FUNCTION__, remaining);
    memset(temp_buf, 0, NET_DATA_SIZE);
    while (remaining > 0) {
        /* Read the data for the request */
        if ((ret = httpd_req_recv(req, temp_buf,
                        MIN(remaining, sizeof(temp_buf)))) <= 0) {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
                /* Retry receiving if timeout occurred */
                continue;
            }
            return ESP_FAIL;
        }
        remaining -= ret;
    }
    if(total_data < NET_DATA_SIZE)
    {
        pRoot = cJSON_Parse(temp_buf);
        if(!pRoot)
        {
            // ESP_LOGE(TAG, "root json error :%s", cJSON_GetErrorPtr());
            paras_form_data(temp_buf, req);
            //return ESP_FAIL;
        }
        else
        {
            printf("JSON ====%s===\r\n", temp_buf);
            paras_json(pRoot, req);
            cJSON_Delete(pRoot);
        }
    }
    else
    {
        printf("Datas is too long\r\n");
    }
    //httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}


/* An HTTP GET handler */
esp_err_t root_get_html(httpd_req_t *req)
{
	/* Send HTML file header */
    httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><meta http-equiv=\"Content-Type\" content=\"text/html\"; charset=\"utf-8\" /><body>");

    /* Get handle to embedded file upload script */
    extern const unsigned char upload_ota_script_start[] asm("_binary_upload_script_html_start");
    extern const unsigned char upload_ota_script_end[]   asm("_binary_upload_script_html_end");
    const size_t upload_ota_script_size = (upload_ota_script_end - upload_ota_script_start);

    /* Add file upload form and script which on execution sends a POST request to /upload */
    httpd_resp_send_chunk(req, (const char *)upload_ota_script_start, upload_ota_script_size);

    /* After sending the HTTP response the old HTTP request
     * headers are lost. Check if HTTP request headers can be read now. */
    if (httpd_req_get_hdr_value_len(req, "Host") == 0) {
        ESP_LOGI(TAG, "root_get_html Request headers lost");
    }
    return ESP_OK;
}

static esp_err_t html_config_handler(httpd_req_t *req)
{
     extern const unsigned char upload_config_script_start[] asm("_binary_serverConfig_html_start");
    extern const unsigned char upload_config_script_end[]   asm("_binary_serverConfig_html_end");
    const size_t upload_config_script_size = (upload_config_script_end - upload_config_script_start);
 
    /* Add file upload form and script which on execution sends a POST request to /upload */
    httpd_resp_set_type(req,HTTPD_TYPE_TEXT);
    httpd_resp_send(req, (const char *)upload_config_script_start, upload_config_script_size);

    return ESP_OK;
}

static const httpd_uri_t post_infos = {
    .uri       = "/setinfo",
    .method    = HTTP_POST,
    .handler   = post_values_handler,
    .user_ctx  = NULL
};

static const httpd_uri_t config_url ={
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = html_config_handler,
};

static const httpd_uri_t ota_url = {
    .uri       = "/device/upgrade",
    .method    = HTTP_GET,
    .handler   = root_get_html,
    /* Let's pass response string in user
     * context to demonstrate it's usage */
    .user_ctx  = "Hello World!"
};

static esp_err_t post_bin_file_handler(httpd_req_t *req)
{
    char ota_write_data[1024];
    int binary_file_length = 0;
    bool image_header_was_checked = false;
    esp_err_t err;
    /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
    esp_ota_handle_t update_handle = 0 ;
    const esp_partition_t *update_partition = NULL;

    update_partition = esp_ota_get_next_update_partition(NULL);
    assert(update_partition != NULL);
    ESP_LOGW(TAG, "Writing to partition subtype %d at offset 0x%lx",
             update_partition->subtype, update_partition->address);

    /*deal with all receive packet*/
    int ret, remaining = req->content_len;
   
    while (remaining > 0)
    {
        ESP_LOGI(TAG, "Remaining size : %d", remaining);
        /* Read the data for the request */
        memset(ota_write_data, 0, 1024);
        ret = httpd_req_recv(req, ota_write_data, MIN(remaining, sizeof(ota_write_data)));
        if (ret <= 0)
        {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT)
            {
                /* Retry receiving if timeout occurred */
                continue;
            }
            return ESP_FAIL;
        }
        else
        {
            if (image_header_was_checked == false)
            {
                image_header_was_checked = true;

                err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
                if (err != ESP_OK)
                {
                    ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                    esp_ota_abort(update_handle);
                }
                ESP_LOGI(TAG, "esp_ota_begin succeeded");
            }
            err = esp_ota_write(update_handle, (const void *)ota_write_data, ret);
            if (err != ESP_OK)
            {
                esp_ota_abort(update_handle);
                return ESP_FAIL;
            }
            binary_file_length += ret;
            ESP_LOGD(TAG, "Written image length %d", binary_file_length);
            remaining -= ret;
        }
    }

    err = esp_ota_end(update_handle);
    if (err != ESP_OK) {
        if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
            ESP_LOGE(TAG, "Image validation failed, image is corrupted");
        } else {
            ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
        }
    }

    err = esp_ota_set_boot_partition(update_partition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
    }
    ESP_LOGI(TAG, "Prepare to restart system!");

    
    httpd_resp_send(req, "{\"code\":200,\"msg\":\"ota ok ,device will reboot\",\"data\":{}}", HTTPD_RESP_USE_STRLEN);

    // End response
    httpd_resp_send_chunk(req, NULL, 0);

    vTaskDelay(1000);

    esp_restart();

    return ESP_OK;
}

static const httpd_uri_t ota_upload = {
    .uri       = "/update",
    .method    = HTTP_POST,
    .handler   = post_bin_file_handler,
    .user_ctx  = NULL
};

static httpd_handle_t start_webserver(void)
{
    int start_temp = 0;
   // httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.recv_wait_timeout = 10;
    config.send_wait_timeout = 10;
    config.lru_purge_enable = true;

    // Start the httpd server
    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    start_temp = httpd_start(&server, &config);
    if (start_temp == ESP_OK) {
        // Set URI handlers
        ESP_LOGI(TAG, "Registering URI handlers");

        httpd_register_uri_handler(server, &config_url);
        httpd_register_uri_handler(server, &ota_url);
        httpd_register_uri_handler(server, &post_infos); 
        httpd_register_uri_handler(server, &ota_upload);        
        
        #if CONFIG_EXAMPLE_BASIC_AUTH
        httpd_register_basic_auth(server);
        #endif
        return server;
    }
    
    ESP_LOGI(TAG, "Error starting server, %d, %s", start_temp, esp_err_to_name(start_temp));
    return NULL;
}

static esp_err_t stop_webserver(httpd_handle_t server)
{
    // Stop the httpd server
    return httpd_stop(server);
}

static void disconnect_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data)
{
    httpd_handle_t* server = (httpd_handle_t*) arg;
    if (*server) {
        ESP_LOGI(TAG, "Stopping webserver");
        if (stop_webserver(*server) == ESP_OK) {
            *server = NULL;
        } else {
            ESP_LOGE(TAG, "Failed to stop http server");
        }
    }
}

static void connect_handler(void* arg, esp_event_base_t event_base,
                            int32_t event_id, void* event_data)
{
    httpd_handle_t* server = (httpd_handle_t*) arg;
    if (*server == NULL) {
        ESP_LOGI(TAG, "Starting webserver");
        // *server = start_webserver();
        start_webserver();
    }
}

void APWebServerStart(void)
{
    if(0 == web_server_init)
    {
        web_server_init = 1;
        // static httpd_handle_t server = NULL;
        ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
        ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));

        // server = start_webserver();
       start_webserver();
    }
}
